# !/usr/bin/env python3
# coding=utf8
"""
基于 vnpy/examples/no_ui/run.py 进行修改,
"""
import multiprocessing
import sys
from time import sleep
from datetime import datetime, time
from logging import INFO

from vnpy.event import EventEngine
from vnpy.trader.setting import SETTINGS
from vnpy.trader.engine import MainEngine

from vnpy_ctp import CtpGateway
from vnpy_ctastrategy import CtaStrategyApp
from vnpy_ctastrategy.base import EVENT_CTA_LOG


import logging
import types
import vnpy_ctastrategy.base
import vnpy_datarecorder.engine
import vnpy_rpcservice.rpc_service
from typing import Dict, List, Set, Tuple, Optional, Union, Callable, Type
from vnpy.trader.engine import BaseGateway, BaseApp
from vnpy_ctastrategy.engine import CtaEngine
from vnpy_ctastrategy.template import CtaTemplate
from vnpy_datarecorder import DataRecorderApp, RecorderEngine
from vnpy_rpcservice import RpcServiceApp
from vnpy_rpcservice.rpc_service import RpcEngine


SETTINGS["log.active"] = True
SETTINGS["log.level"] = INFO
SETTINGS["log.console"] = True


ctp_setting = {
    "用户名": "",
    "密码": "",
    "经纪商代码": "",
    "交易服务器": "",
    "行情服务器": "",
    "产品名称": "",
    "授权编码": "",
    "产品信息": ""
}


# Chinese futures market trading period (day/night)
DAY_START = time(8, 45)
DAY_END = time(15, 35)

NIGHT_START = time(20, 45)
NIGHT_END = time(2, 45)


def check_trading_period():
    """"""
    current_time = datetime.now().time()

    trading = False
    if (
        (current_time >= DAY_START and current_time <= DAY_END)
        or (current_time >= NIGHT_START)
        or (current_time <= NIGHT_END)
    ):
        trading = True

    return trading


def print_log(*values: object) -> None:
    """"""
    print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), *values)


def run_child_deprecated():
    """
    Running in the child process.
    """
    SETTINGS["log.file"] = True

    event_engine = EventEngine()
    main_engine = MainEngine(event_engine)
    main_engine.add_gateway(CtpGateway)
    cta_engine = main_engine.add_app(CtaStrategyApp)
    main_engine.write_log("主引擎创建成功")

    log_engine = main_engine.get_engine("log")
    event_engine.register(EVENT_CTA_LOG, log_engine.process_log_event)
    main_engine.write_log("注册日志事件监听")

    main_engine.connect(ctp_setting, "CTP")
    main_engine.write_log("连接CTP接口")

    sleep(10)

    cta_engine.init_engine()
    main_engine.write_log("CTA策略初始化完成")

    cta_engine.init_all_strategies()
    sleep(60)   # Leave enough time to complete strategy initialization
    main_engine.write_log("CTA策略全部初始化")

    cta_engine.start_all_strategies()
    main_engine.write_log("CTA策略全部启动")

    while True:
        sleep(10)

        trading = check_trading_period()
        if not trading:
            print_log("关闭子进程")
            main_engine.close()
            sys.exit(0)


class ChildExecutor(object):
    """
    基于 vnpy/examples/no_ui/run.py 进行修改,
    """
    ALLOW_BLOCK: bool = True

    def __init__(self) -> None:
        """"""
        # event_engine = EventEngine()
        # main_engine = MainEngine(event_engine)
        # main_engine.add_gateway(CtpGateway)
        # cta_engine = main_engine.add_app(CtaStrategyApp)
        # main_engine.write_log("主引擎创建成功")

        # log_engine = main_engine.get_engine("log")
        # event_engine.register(EVENT_CTA_LOG, log_engine.process_log_event)
        # main_engine.write_log("注册日志事件监听")

        self.event_engine = EventEngine()
        self.main_engine = MainEngine(self.event_engine)

        self._ctp_gateway: CtpGateway = self.main_engine.add_gateway(CtpGateway)
        assert (self._ctp_gateway.gateway_name == CtpGateway.default_name)  # 后续要用这个名字取对象,

        self._cta_engine: CtaEngine = self.main_engine.add_app(CtaStrategyApp)

        self._recorder_engine: RecorderEngine = self.main_engine.add_app(DataRecorderApp)

        self._rpc_engine: RpcEngine = self.main_engine.add_app(RpcServiceApp)

        self.main_engine.write_log("主引擎创建成功")

        self.log_engine = self.main_engine.get_engine("log")
        self.event_engine.register(EVENT_CTA_LOG, self.log_engine.process_log_event)
        self.main_engine.write_log("注册日志事件监听")

    @classmethod
    def connect_all_gateway(cls, main_engine: MainEngine) -> None:
        """"""
        # main_engine.connect(ctp_setting, "CTP")
        # main_engine.write_log("连接CTP接口")

        gateway_names: List[str] = main_engine.get_all_gateway_names()
        for gateway_name in gateway_names:
            setting: dict = cls.load_gateway_setting(gateway_name=gateway_name)
            main_engine.connect(setting=setting, gateway_name=gateway_name)
            main_engine.write_log(f"connect gateway_name={gateway_name}")

    @classmethod
    def is_ready_CtpGateway(cls, main_engine: MainEngine, timeout: Optional[int] = None) -> bool:
        """"""
        if not cls.ALLOW_BLOCK:
            return False

        assert isinstance(timeout, (types.NoneType, int))

        ctp_gateway: CtpGateway = main_engine.get_gateway(CtpGateway.default_name)
        assert isinstance(ctp_gateway, CtpGateway)

        timeout: int = 20 if (timeout is None or timeout <= 0) else timeout

        is_ok: bool = False

        for num in range(1, timeout + 1, 1):
            if (
                True
                and ctp_gateway.md_api.login_status == True
                and ctp_gateway.td_api.login_status == True
                and ctp_gateway.td_api.contract_inited == True
            ):
                is_ok: bool = True
                break

            if num < timeout:
                sleep(1)

        return is_ok

    @classmethod
    def initialize_cta_engine(cls, main_engine: MainEngine) -> None:
        """"""
        # cta_engine.init_engine()
        # main_engine.write_log("CTA策略初始化完成")

        # cta_engine.init_all_strategies()
        # sleep(60)   # Leave enough time to complete strategy initialization
        # main_engine.write_log("CTA策略全部初始化")

        # cta_engine.start_all_strategies()
        # main_engine.write_log("CTA策略全部启动")

        cta_engine = main_engine.get_engine(vnpy_ctastrategy.base.APP_NAME)
        assert isinstance(cta_engine, CtaEngine)

        recorder_engine = main_engine.get_engine(vnpy_datarecorder.engine.APP_NAME)
        assert isinstance(recorder_engine, RecorderEngine)

        cta_engine.init_engine()
        main_engine.write_log("CTA策略初始化完成")

        cta_engine.init_all_strategies()
        for strategy in cta_engine.strategies.values():
            strategy: CtaTemplate = strategy
            recorder_engine.add_tick_recording(strategy.vt_symbol)
            recorder_engine.add_bar_recording(strategy.vt_symbol)

        # Leave enough time to complete strategy initialization
        cls.is_inited_strategies(strategies=cta_engine.strategies, timeout=120)

        inited_num: int = len([_ for _ in cta_engine.strategies.values() if _.inited == True])
        main_engine.write_log(f"CTA策略共有{len(cta_engine.strategies)}个, 始化完成{inited_num}个")

        main_engine.write_log("CTA策略全部初始化")

        cta_engine.start_all_strategies()
        main_engine.write_log("CTA策略全部启动")

    @classmethod
    def initialize_rpc_engine(cls, main_engine: MainEngine) -> None:
        """"""
        rpc_engine: RpcEngine = main_engine.get_engine(vnpy_rpcservice.rpc_service.APP_NAME)
        assert isinstance(rpc_engine, RpcEngine)

        rpc_engine.start(rep_address=rpc_engine.rep_address, pub_address=rpc_engine.pub_address)

        main_engine.write_log("RPC服务启动结束")

    @classmethod
    def run(cls, main_engine: MainEngine):
        """"""
        while True:
            sleep(10)

            trading = check_trading_period()
            if not trading:
                print_log("关闭子进程")
                main_engine.close()
                sys.exit(0)

    @classmethod
    def load_gateway_setting(cls, gateway_name: str) -> dict:
        """
        CTP 的 gateway_name="CTP"
        参考了 vnpy/vnpy/trader/ui/widget.py 的 ConnectDialog 的逻辑,
        """
        from vnpy.trader.utility import load_json

        self_filename: str = f"connect_{gateway_name.lower()}.json"

        # Saved setting provides field data used last time.
        loaded_setting: dict = load_json(self_filename)

        return loaded_setting

    @classmethod
    def is_inited_strategies(cls, strategies: Dict[str, CtaTemplate], timeout: Optional[int] = None) -> bool:
        """"""
        if not cls.ALLOW_BLOCK:
            return False

        assert isinstance(timeout, (types.NoneType, int))

        timeout: int = 120 if (timeout is None or timeout <= 0) else timeout

        is_ok: bool = False

        for num in range(1, timeout + 1, 1):
            inited_num: int = len([strategy for strategy in strategies.values() if strategy.inited == True])

            if inited_num == len(strategies):
                is_ok: bool = True
                break

            if num < timeout:
                sleep(1)

        return is_ok


def run_child():
    """
    Running in the child process.
    """
    SETTINGS["log.file"] = True

    child = ChildExecutor()
    child.connect_all_gateway(main_engine=child.main_engine)
    child.is_ready_CtpGateway(main_engine=child.main_engine, timeout=120)
    child.initialize_cta_engine(main_engine=child.main_engine)
    child.initialize_rpc_engine(main_engine=child.main_engine)
    child.run(main_engine=child.main_engine)


def run_parent():
    """
    Running in the parent process.
    """
    print_log("启动CTA策略守护父进程")

    child_process = None

    while True:
        trading = check_trading_period()

        # Start child process in trading period
        if trading and child_process is None:
            print_log("启动子进程")
            child_process = multiprocessing.Process(target=run_child)
            child_process.start()
            print_log("子进程启动成功" + f", pid={child_process.pid}")

        # 非记录时间则退出子进程
        if not trading and child_process is not None:
            if not child_process.is_alive():
                child_process = None
                print_log("子进程关闭成功")

        sleep(5)


# vnpy_ctacli 中 的 cli 取的是"GUI/CLI/TUI"里的 CLI 的意思,
# 至少需要准备好下面的文件:
# connect_ctp.json
# cta_strategy_setting.json
# rpc_service_setting.json 会自动生成, 不用过于关心,
if __name__ == "__main__":
    # 创造一个日志记录（LogRecord）并传递该日志输出的事件；
    # Logger 作为日志模块树的一个节点，将该事件往根部传递，
    # 所有处于传递路径（该 logger->parent logger -> … -> root logger）上的所有 logger 节点添加的所有 Handler 都会相应该事件。
    # 当然，通过设置 logger 的 propagate = False 或者没有 parent logger 可以阻止事件传播。
    logger = logging.getLogger("peewee")
    logger.propagate = False

    run_parent()
